
package ploy.compiler;

import java.io.*;
import java.util.*;
import java.text.*;

import org.objectweb.asm.*;
import org.objectweb.asm.util.*;
import org.objectweb.asm.commons.*;

import ploy.lang.*;

/**
 * Represents an abstract syntax tree for a parsed Ploy program.
 * Also contains functions for parsing a Ploy program.
 */
public abstract class AST {

    private static class EmptyThunk extends Closure {
	
	public EmptyThunk( ) {
	    super( 0 );
	}

	public DynamicObject invoke0( ) 
	    throws RuntimeError
	{
	    return new Nil();
	}
    }

    public static final PloyClassLoader ployClassLoader = 
	new PloyClassLoader( );

    private static final EmptyThunk emptyThunk = new EmptyThunk( );

    /** 
     * Parses concrete syntax (i.e. an s-expression) into an AST.  Parses
     * in the context of a parse environment.
     * 
     * @param s the s-expression to be parsed
     *
     * @param m the parse environment that is the current static context
     *
     * @return An AST representing the Ploy program s.
     *
     */
    public static AST parse( DynamicObject s, SyntacticEnvironment m ) 
	throws ParseException
    {
	ParseClass pc = null;

	if (s.isPair()) {
	    Pair p = (Pair)s;
	    if (p.head().isSymbol()) {
		pc = m.keywordParser((Symbol)p.head());
		if (pc == null)
		    pc = m.keywordParser(PloyFundamentals.appSymbol);
	    } else {
		pc = m.keywordParser(PloyFundamentals.appSymbol);
	    }
	} else if (s.isSymbol()) {
	    pc = m.keywordParser(PloyFundamentals.varSymbol);
	} else {
	    pc = m.keywordParser(PloyFundamentals.constSymbol);
	}
	if (pc == null)
	    throw new ParseException( "Cannot parse form", 0 );
	return pc.parse(s,m);

    }

    /**
     * Compiles the AST a into bytecode, loads it, and returns the closure
     * representing a thunk which, when invoked, will execute the program a.
     *
     * @param a the AST representing the program to be compiled
     *
     * @param cl the classloader to use to load the bytecode
     *
     * @return A thunk that, when invoked, executes program a.
     *
     */
    public static Closure compileThunk( AST a, 
					PloyClassLoader cl ) 
	throws ParseException
    {

	if ( !a.hasRuntimeSemantics() ) {
	    return emptyThunk;
	}
        AST wrapAST = new Lambda( new LinkedList<Symbol>(), a );
        
	wrapAST.locateVariables( new VarLocationTable( ) );

        TreeSet<Lambda> lambdas = wrapAST.allLambdas( );
        
        Iterator<Lambda> i = lambdas.iterator( );

        while ( i.hasNext( ) ) {
            Lambda l = i.next( );
            ClassWriter cv = new ClassWriter(0);
	    TraceClassVisitor tcv = 
		new TraceClassVisitor(cv, new PrintWriter(System.out, true));
            l.generateClass(tcv);
            byte[] b = cv.toByteArray();
            cl.defineClass( l.generatedName.toString(), b );
        }
        
	try {
	    Class c = 
		cl.loadClass( ((Lambda)wrapAST).generatedName.toString() );
	    Closure closure = (Closure)(c.newInstance());
	    return (Closure)(c.newInstance());
	} catch ( ClassFormatError c ) {
	    throw new ParseException( "(INTERNAL ERROR CF) " + 
				      c.getMessage( ), 0 );
	} catch ( ClassNotFoundException c ) {
	    throw new ParseException( "(INTERNAL ERROR CNF) " + 
				      c.getMessage( ), 0 );
	} catch ( InstantiationException c ) {
	    throw new ParseException( "(INTERNAL ERROR IE) " + 
				      c.getMessage( ), 0 );
	} catch ( IllegalAccessException c ) {
	    throw new ParseException( "(INTERNAL ERROR IA) " + 
				      c.getMessage( ), 0 );
	}
    }
    
    // The rest of these are abstract, to be invoked on subclasses.

    /**
     * Generate a string representation of the AST.
     */
    public abstract String toString( );

    /**
     * Calculate the free variables of the AST.  This is to be overridden,
     * but the actual entry point (freeVars) below will memoize the set.
     *
     * @return A set containing all free variables of the AST.
     *
     */
    protected TreeSet<Symbol> calculateFreeVars( ) {
	return new TreeSet<Symbol>();
    }

    private TreeSet<Symbol> freeVars = null;

    /**
     * Calculate the free variables of the AST and memoize it.
     *
     * @return A set containing all free variables of the AST.
     *
     */
    public final TreeSet<Symbol> freeVars( ) {
	if (freeVars == null) {
	    freeVars = calculateFreeVars( );
	}
	return freeVars;
    }

    /**
     * Generate bytecode for this AST.
     *
     * @param m the methodvisitor used to generate instructions
     *
     * @return The FrameSize object representing the frame variables
     *         and stack used by the bytecode.
     */
    public abstract FrameSize generateBytecode( MethodVisitor m );

    /**
     * Gather all of the lambdas used in this AST (for lifting).
     *
     * @return A set contianing all of the lambdas in this expression.
     *
     */
    public TreeSet<Lambda> allLambdas( ) {
	return new TreeSet<Lambda>( );
    }

    /**
     * Using this variable location table, mark all variable AST nodes
     * with their appropriate location.
     *
     * @param t a table containing all lexically enclosing variable definitions
     *
     */
    public void locateVariables( VarLocationTable t ) {
	return;
    }

    public boolean hasRuntimeSemantics( ) {
	return true;
    }

}